Beyond cycle.js sesパターン
hiroqn
Cycle.js 5個めぐらい
株式会社HERP
Node.js(koa) TypeScript Haskell(Yesod)
最近の仕事
nix
k8s
groovy
About cycle.js
特徴
Stream(RxJS, xstream, most)
-> 次の計算の抽象化
snabbdom
仮想DOM(VNode)
Driver
-> IO, Resource管理, DI,
isolate
-> 分離(Component化)
Stream
計算の順番を抽象化して扱える
「Promiseの値が複数」という解釈は正直雑
DOM Eventを例に上げると
addEventListenerするタイミング
Eventが発火されたタイミング
removeEventListernerするタイミング
例
「ボタンAがクリックされたあとボタンBをクリックをされた時にXをする」という行為を2回だけしたい
🙆A->B->X -> A -> A->B->X
🙅A->A->B->B->X->X
正しい順番
AにaddEventListener
Aのイベントが発火(クリック)
BにaddEventListener
Bのイベントが発火(クリック)
BをremoveEventListener
X
繰り返してXが2回実行された時AをremoveEventListener
雑コード
code:js
streamA
.switchMap(_ => streamB.take(1))
.take(2)
.subscribe(_ => X())
Streamを一言で言うなら、遅延評価とリソース管理!
Stream<VNode>
snabbdomはただの仮想DOMライブラリ
VNodeのStreamがあればDOMにレンダリングできる
Cycle.js は何をやってくれる?
Eventはいつ生まれるか?
ボタンAのクリックイベントが生まれるタイミングは?
ボタンAがレンダリングされたあと
code:js
const elementStream = streamVNode.reduce(patchVNode, domElement)
// これは実DOMのElement
const clickEventStream = elementStream.switchMap(fromEvent)
// EventのStreamを使ってVNodeのStreamつくれない??
Driver:上に持ってくる
上に持ってくるとは?
Streamは本来一方向->Driverがつないで輪にしくれる
だからCycle
https://blogimg.goo.ne.jp/user_image/22/ab/9b3f2457a11bb36bdae69795f41c6c6c.jpg
Driver:Input (source)
外側の値を取り込む必要がある時
Cycle.jsの外からの値とか???
タブのアクティブ・非アクティブ
windowのリサイズ
@cycle/time
Streamライブラリの中でやると、、、
テスト
タイミングコントロールが甘い
Driver:Output (sink)
StreamはSubscribeしないと流れない
結果はどうでもいいもの
Google Analytics イベントの送信
Favicon の変更
example
code:typescript
import { run } from '@cycle/run';
import { div, DOMSource, makeDOMDriver } from '@cycle/dom';
import Stream from "xstream";
import { VNode } from 'snabbdom/vnode';
export type SoDOM = { DOM: DOMSource };
export type SiDOM = { DOM: Stream<VNode> };
run<SoDOM, SiDOM>(function ({DOM}) {
return {
DOM: DOM.select(.btn).events('click')
.mapTo(1)
.fold((acc, x) => acc + x, 0)
.map(n => div('.btn', [Button ${n}]))
}
}, {
DOM: makeDOMDriver('#app'),
});
isolate
Component化を助けるため
今回説明省きます
Deisgn Patern
MVI パターン
Model-View-Intent
公式に書いてある
fluxっぽい
SES パターン
HERPで使用
What's SES?
Stream -> Endo -> State
Stream
まぁStreamですね
Endo
endomorphism:自己準同型
$ f: X \to X
type Endo<T> = (x: T) => T
State
State:アプリケーションの状態とか
code:ts
export type Endo<T> = (x: T) => T;
export type SES = Stream<Endo<State>>
const type ses: SES = hoge
const state$: Stream<State> = ses.fold((prevState: State, endo: Endo<State>) => endo(prevState), initialState)
Why Endo
合成しやすい(Composable)
Intentと比べてswitchを書く必要がない
Cycle.jsはあるComponentのなかで状態を持つ
なぜならCycleのComponentはReactのComponentと比べてDOMにフォーカスしない
(export type Component<So, Si> = (sources: So, ...rest: Array<any>) => Si;)
code:ts
export function evolveC<T extends object>(transformations: { P in keyof T: Endo<TP> }): <U extends {P in keyof T: TP}>(x: U) => U { const newStruct: U = Object.assign({}, x);
for (const key in transformations) {
const f = transformationskey; newStructkey = f(newStructkey); }
return newStruct;
}
}
export function modifyC<K extends string, T>(key: K, f: Endo<T>): <U extends {P in K: T}>(x: U) => U { return function <U extends {P in K: T}>(struct: U): U { const newStruct: U = Object.assign({}, struct);
return newStruct;
}
}
export function modifyToC<K extends string, T>(key: K, v: T): <U extends {P in K: T}>(x: U) => U { return function <U extends {P in K: T}>(struct: U): U { const newStruct: U = Object.assign({}, struct);
return newStruct;
}
}
endo example
code:ts
type State = {
name: string
isActive: boolean
}
modifyC('name', name => name + name) // Endo<State>
modifyToC('isActive', false) // Endo<State>
evolveC({
name: (name: string): string => name + name,
isActive: (_: boolean): boolean => false
})// Endo<State>
count up
code:ts
type CountUpState = {
count: number
}
function accumulator<T>(state: T, endo: Endo<T>): T {
return endo(state);
}
function countUp({ DOM }: SoDOM): SiDOM {
const state$: Stream<CountUpState> = DOM.select(.evt-click-btn).events('click')
.mapTo(modifyC('count', (x: number): number => x + 1))
.fold(accumulator, { count: 0 });
return {
DOM: state$.map(({ count }) => div('.frame', [
button('.evt-click-btn', [Button ${count}])
]))
}
}
MouseStalker
code:typescript
type MouseStalkerState = {
color: string
}
Beyond
型が弱い
構文が弱い
EventとBehaviorが型レベルで、、、
StreamとFuture供用、Streamは抽象度が高い
ていうかもうちょっと型が強ければStreamの種類分けて良くない?(libuv, とかreqAnime, 特性が違う)
逆にResource管理を強くして、DOMにフォーカスしてReactぽさも
雑
Event Behavior
Codegen
code:typescript
export type HttpClient = (req: HttpClientRequest, cb: (res: HttpClientResponse) => void) => (() => void)
export type PureFuture<T, E> = (cb: (x: Result<T, E>) => void) => (() => void)
// HttpClient => PureFuture
// PureFutureは fmapもbindも可能!